
#include "graphics/octree.h"
#include "graphics/graphicsmanager.h"
#include "graphics/renderer.h"
#include "graphics/node.h"
#include "graphics/boundingvolume.h"
#include "graphics/object.h"
#include "graphics/frustum.h"

#define OCTREE_MAX_ELEMENTS 4
#define OCTREE_MAX_DEPTH		8

namespace dw
{
namespace graphics
{

Octree::Octree(const unsigned depth) : _depth(depth)
{
	memset(_children, 0, sizeof (Node*));
}

Octree::Octree(const Point3f& min, const Point3f& max, const unsigned depth) : 
	_boundingBox(min, max), _depth(depth)
{
	memset(_children, 0, sizeof (Node*));
}

Octree::~Octree()
{
	if (_children[0])
		for (unsigned i = 0; i < 8; ++i)
			delete _children[i];
}

void Octree::insertElement(Node* element)
{
	if (!element) throw std::invalid_argument(__FUNCTION__);
		_elements.insert(element);
}

void Octree::removeElement(Node* element)
{
	_elements.erase(element);
}

void Octree::subdivide()
{
	if (this->hasChildren()) throw std::logic_error(__FUNCTION__);

	Point3f min			= _boundingBox.center - _boundingBox.size / 2.0f;
	Point3f max			= _boundingBox.center + _boundingBox.size / 2.0f;
	Point3f center	= _boundingBox.center;

	_children[0] = new Octree(Point3f(min.x, center.y, min.z), Point3f(center.x, max.y, center.z), _depth + 1);
	_children[1] = new Octree(Point3f(center.x, center.y, min.z), Point3f(max.x, max.y, center.z), _depth + 1);
	_children[2] = new Octree(center, max, _depth + 1);
	_children[3] = new Octree(Point3f(min.x, center.y, center.z), Point3f(center.x, max.y, max.z), _depth + 1);
	_children[4] = new Octree(min, center, _depth + 1);
	_children[5] = new Octree(Point3f(center.x, min.y, min.z), Point3f(max.x, center.y, center.z), _depth + 1);
	_children[6] = new Octree(Point3f(center.x, min.y, center.z), Point3f(max.x, center.y, max.z), _depth + 1);
	_children[7] = new Octree(Point3f(min.x, min.y, center.z), Point3f(center.x, center.y, max.z), _depth + 1);

	std::set< Node* >::iterator iter;
	for (iter = _elements.begin(); iter != _elements.end(); ++iter)
	{
		Node* element = *iter;
		for (unsigned i = 0; i < 8; ++i)
		{
			if (_children[i]->intersects(element))
			{
				_children[i]->insertElement(element);
			}
		}
	}
}

void Octree::collapse()
{
	if (!this->hasChildren())
	{
		for (unsigned i = 0; i < 8; ++i)
		{
			_children[i]->collapse();
			_elements.insert(_children[i]->_elements.begin(), _children[i]->_elements.end());
			delete _children[i];
			_children[i] = 0;
		}
	}
}

void Octree::balance()
{
	if (this->hasChildren() && (_elements.size() <= OCTREE_MAX_ELEMENTS))
		this->collapse();
	if (!this->hasChildren() && (_depth <= OCTREE_MAX_DEPTH) && (_elements.size() > OCTREE_MAX_ELEMENTS))
		this->subdivide();
	if (this->hasChildren())
	{
		for (unsigned i = 0; i < 8; ++i)
			_children[i]->balance();
	}
}

bool Octree::intersects(Node* element)
{
	return _boundingBox.intersects(element->getBoundingBox());
}

void Octree::render(Frustum* frustum)
{
	if (!frustum) throw std::invalid_argument(__FUNCTION__);

	IntersectionInfo intersectionInfo;
	if (frustum->intersects(this->_boundingBox, intersectionInfo))
	{
		if (intersectionInfo.type == IntersectionInfo::INTERSECT)
		{
			if (this->hasChildren())
			{
				for (unsigned i = 0; i < 8; ++i)
					_children[i]->render(frustum);
			}
			else 
				GraphicsManager::getInstance().getRenderer().drawBox(_boundingBox);
		}
		else if (intersectionInfo.type == IntersectionInfo::INSIDE)
			GraphicsManager::getInstance().getRenderer().drawBox(_boundingBox);
	}
}

} // namespace graphics
} // namespace dw
